package xuecheng.tool.http.impl;

import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.TimeUnit;

import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;

import com.google.common.collect.Maps;

import cn.hutool.core.util.StrUtil;
import lombok.extern.slf4j.Slf4j;
import okhttp3.*;
import xuecheng.tool.core.datetime.DateTimeUtils;
import xuecheng.tool.http.CurlUtils;
import xuecheng.tool.http.HttpUtils;
import xuecheng.tool.http.SimpleEvent;
import xuecheng.tool.json.JsonUtils;
import xuecheng.tool.json.JsonUtilsFactory;

/**
 * okhttp3 实现
 *
 * @author PengXueCheng
 * @version 1.0.0
 */
@Slf4j
public class HttpUtilsOkHttp3Impl implements HttpUtils {

    /**
     * json 工具类
     */
    private final JsonUtils JSON_UTILS = JsonUtilsFactory.obtain();

    /**
     * okhttp3 工具类
     */
    private final OkHttpClient okHttpClient;

    /**
     * @param callTimeout
     *            完整调用的默认超时(单位为毫秒) 为空则不设置
     */
    public HttpUtilsOkHttp3Impl(Integer callTimeout) {
        OkHttpClient.Builder builder = new OkHttpClient.Builder();
        if (null != callTimeout) {
            builder.callTimeout(callTimeout, TimeUnit.MILLISECONDS);
        }
        okHttpClient =
            // 解决 EOF 异常
            builder.retryOnConnectionFailure(true).addInterceptor(new LoggingInterceptor()).build();
    }

    @Override
    public SimpleEvent get(String url, Map<String, String> headers) {
        headers = Optional.ofNullable(headers).orElse(Maps.newTreeMap());
        String curl = CurlUtils.get(url, "", headers);
        log.info(curl);

        Request.Builder builder = new Request.Builder();
        headers.forEach(builder::addHeader);
        Request request = builder.url(url).build();
        SimpleEvent simpleEvent = getHttpResult(request);
        simpleEvent.setCurl(curl);
        return simpleEvent;
    }

    @Override
    public SimpleEvent get(String url) {
        return get(url, null);
    }

    @Override
    public SimpleEvent post(String url, Object contentObject) {
        String content = JSON_UTILS.toJson(contentObject);
        return post(url, content);
    }

    @Override
    public SimpleEvent post(String url, String content) {
        return post(url, content, StrMediaType.MEDIA_TYPE_JSON, Maps.newTreeMap());
    }

    @Override
    public SimpleEvent post(String url, String content, Map<String, String> headers) {
        String mediaType = headers.get("content-type");
        if (StringUtils.isEmpty(mediaType)) {
            throw new IllegalArgumentException("missing header parameter content-type");
        }
        return post(url, content, mediaType, headers);
    }

    @Override
    public SimpleEvent post(String url, byte[] content, Map<String, String> headers) {
        String mediaType = headers.get("content-type");
        if (StringUtils.isEmpty(mediaType)) {
            throw new IllegalArgumentException("missing header parameter content-type");
        }
        return post(url, content, mediaType, headers);
    }

    @Override
    public SimpleEvent post(String url, String content, String mediaType, Map<String, String> headers) {
        headers = Optional.ofNullable(headers).orElse(Maps.newTreeMap());
        headers.put("content-type", mediaType);
        String curl = CurlUtils.post(url, content, headers);
        log.info(curl);

        Request.Builder builder = new Request.Builder();
        headers.forEach(builder::addHeader);
        Request request = builder.url(url)
            .post(RequestBody.create(MediaType.parse(mediaType), StringUtils.defaultIfEmpty(content, "{}"))).build();
        SimpleEvent simpleEvent = getHttpResult(request);
        simpleEvent.setCurl(curl);
        return simpleEvent;
    }

    @Override
    public SimpleEvent post(String url, byte[] content, String mediaType, Map<String, String> headers) {
        headers = Optional.ofNullable(headers).orElse(Maps.newTreeMap());
        headers.put("content-type", mediaType);
        String curl = CurlUtils.post(url, StrUtil.str(content, StandardCharsets.UTF_8.name()), headers);
        log.info(curl);

        Request.Builder builder = new Request.Builder();
        headers.forEach(builder::addHeader);
        Request request = builder.url(url)
            .post(RequestBody.create(MediaType.parse(mediaType), ObjectUtils.defaultIfNull(content, new byte[0])))
            .build();
        SimpleEvent simpleEvent = getHttpResult(request);
        simpleEvent.setCurl(curl);
        return simpleEvent;
    }

    /**
     * 发送请求得到 httpResult
     *
     * @param request
     *            request 要发送的请求
     * @return http 响应结果
     */
    private SimpleEvent getHttpResult(Request request) {
        Response response;
        Call call = okHttpClient.newCall(request);
        long t1 = System.nanoTime();
        try {
            response = call.execute();
        } catch (IOException e) {
            long t2 = System.nanoTime();
            log.error(okHttpClient.toString() + "_" + request.toString() + "_" + e.getMessage());
            return SimpleEvent.builder().e(e).consumeTime(DateTimeUtils.getConsumeTime(t1, t2)).build();
        }
        long t2 = System.nanoTime();
        ResponseBody body = response.body();
        assert body != null;
        SimpleEvent event = SimpleEvent.builder().responseStatus(response.code()).charStream(body.charStream())
            .byteStream(body.byteStream()).consumeTime(DateTimeUtils.getConsumeTime(t1, t2)).build();
        try {
            event.setString(body.string());
        } catch (IOException e) {
            event.setE(e);
            log.error(event.toString() + "_" + body.toString() + "_" + e.getMessage());
        }
        return event;
    }

    /**
     * 日志拦截器
     */
    private static class LoggingInterceptor implements Interceptor {

        @Override
        public Response intercept(Chain chain) throws IOException {
            Request request = chain.request();

            log.info(
                String.format("Sending request %s on %s%n%s", request.url(), chain.connection(), request.headers()));

            long t1 = System.nanoTime();
            Response response = chain.proceed(request);
            long t2 = System.nanoTime();

            double consumeTime = DateTimeUtils.getConsumeTime(t1, t2);
            log.info(String.format("Received response for %s in %.1fms%n%s", response.request().url(), consumeTime,
                response.headers()));

            return response;
        }
    }

}
